package xyz.scootaloo.kami.server.service

import xyz.scootaloo.kami.server.model.*
import xyz.scootaloo.kami.server.standard.AccessDeniedException
import xyz.scootaloo.kami.server.standard.CreateIllegalLimitException
import xyz.scootaloo.kami.server.standard.UploadExceedLimitException
import java.util.*

/**
 * 维护由哨兵节点构成的虚拟文件系统
 *
 * - [access]: 当需要对文件系统执行某操作时, 如果达不到执行操作需要的等级, 则操作被虚拟文件系统拒绝
 * - 每个哨兵节点都有具备监听文件系统对应路径的容量使用情况的能力([SentryNode.capacityAvailable]返回true时)
 * - 通过扩展[SentryNode]可以让哨兵具备更多能力
 *
 * @author flutterdash@qq.com
 * @since 2022/3/22 17:12
 */
object VirtualFileSystem {

    val editor = Editor(this)
    val viewer = Viewer(this)

    private val root: SentryNode = SentryNode.createEmptyNode("/", null)

    @Throws(AccessDeniedException::class)
    fun access(token: AccessToken): SentryNode {
        if (token.level == AccessLevel.SUPER_ADMIN)
            return SentryNode.FAKE_NODE

        val (key, node, hit) = findAndMatch(token.path) { node ->
            Checker.checkPermission(token, node)
            node.valid
        }

        return if (hit) node.copy() else findNode(key).copyWithRule()
    }

    private fun isRoot(path: String): Boolean {
        return path == "/" || path.isEmpty() || path.isBlank()
    }

    private fun findOrCreateNode(path: String): SentryNode {
        return findOrCreateNode(splitPath(path))
    }

    private fun findOrCreateNode(pathItems: List<String>): SentryNode {
        if (pathItems.isEmpty())
            return root

        var currentNode = root
        for (idx in pathItems.indices) {
            val itemKey = pathItems[idx]
            if (currentNode.containsSubItem(itemKey)) {
                currentNode = currentNode.subItems[itemKey]!!
            } else {
                val newNode = SentryNode.createEmptyNode(itemKey, currentNode)
                currentNode.subItems[itemKey] = newNode
                currentNode = newNode
            }
        }
        return currentNode
    }

    private fun findNode(path: String): SentryNode {
        return findNode(splitPath(path))
    }

    private fun findNode(pathItems: List<String>): SentryNode {
        if (pathItems.isEmpty())
            return root

        var currentNode = root
        for (idx in pathItems.indices) {
            val itemKey = pathItems[idx]
            if (currentNode.containsSubItem(itemKey)) {
                currentNode = currentNode.subItems[itemKey]!!
            } else {
                throw NullPointerException()
            }
        }

        return currentNode
    }

    private inline fun findAndMatch(
        path: String, match: (SentryNode) -> Boolean
    ): Triple<List<String>, SentryNode, Boolean> {
        if (isRoot(path))
            return Triple(emptyList(), root, true)

        var lastMatchedNode = root
        var currentNode = root

        val pathItems = splitPath(path)
        for (idx in pathItems.indices) {
            val itemKey = pathItems[idx]
            if (currentNode.containsSubItem(itemKey)) {
                val nextNode = currentNode.subItems[itemKey]!!
                if (nextNode.valid && match(nextNode)) {
                    lastMatchedNode = nextNode
                }
                currentNode = nextNode
            } else {
                // 线索丢失
                return Triple(lastMatchedNode.buildChain(), SentryNode.FAKE_NODE, false)
            }
        }

        return Triple(lastMatchedNode.buildChain(), currentNode, true)
    }

    fun parent(node: SentryNode): SentryNode {
        return node.parent ?: SentryNode.FAKE_NODE
    }

    private fun splitPath(path: String): List<String> {
        return path.replace('\\', '/').split('/').filter {
            it.isNotBlank()
        }
    }

    object Checker {
        @Throws(AccessDeniedException::class)
        fun checkPermission(token: AccessToken, rule: SentryNode) {
            if (!rule.valid)
                return

            for (type in token.types) {
                if (!isAllow(token.level, type, rule.rule))
                    throw accessDenied(type)
            }
        }

        fun isAllow(userLevel: Int, token: AccessType, rule: ByteArray): Boolean {
            val ruleLevel = rule[token.key]
            return ruleLevel <= userLevel
        }
    }

    class Editor(private val ref: VirtualFileSystem) {
        fun insertByDatabaseRule(dbRule: SysRule) {
            insertRule(dbRule.path, dbRule.accessRule).apply {
                updateCreateInfo(dbRule.created, dbRule.updated)
            }
        }

        fun insertByDatabaseLimit(dbLimit: SysLimit) {
            insertLimit(dbLimit.path, dbLimit.capacity).apply {
                updateCreateInfo(dbLimit.created, dbLimit.updated)
            }
        }

        fun insertRule(path: String, rule: ByteArray): SentryNode {
            return ref.findOrCreateNode(path).apply { updateRule(rule) }
        }

        /**
         * 创建的空间限制[limit], 必须小于所有祖先节点的空间限制,
         * 如果此路径的子孙路径也存在空间限制, 则当前空间限制必须大于任意子孙节点的空间限制
         */
        fun insertLimit(path: String, limit: Long): SentryNode {
            fun report(node: SentryNode) {
                val err = createIllegalLimitError(path, node)
                deepDeleteNode(node)
                throw err
            }

            val node = ref.findOrCreateNode(path)

            val supNodes = viewer.listSupNodes(node) { it.hasCapacityRestrict() }
            val subNodes = viewer.listSubNodes(node) { it.hasCapacityRestrict() }

            if (supNodes.any { it.capacityLimit() < limit }) {
                report(node)
            }
            if (subNodes.any { it.capacityLimit() > limit }) {
                report(node)
            }

            node.updateCapLimit(limit)

            return node
        }

        fun shrinkageCapacity(path: String, amount: Long, onChange: CapacityChangeHandler) {
            if (amount == 0L)
                return

            val (key, node, hit) = ref.findAndMatch(path) { it.hasCapacityRestrict() }
            val endpoint = if (hit) node else ref.findNode(key)
            var currentNode = endpoint

            // 向上搜索, 检查每一个祖先节点, 剩余的可用空间是否足够放置新的文件
            // 假如有一个节点检查不通过, 则立即抛出异常
            do {
                if (currentNode.hasCapacityRestrict()) {
                    if (currentNode.capacityAvailable() < amount) {
                        throw capacityShortageException(path, currentNode)
                    }
                }
                currentNode = parent(currentNode)
            } while (currentNode != SentryNode.FAKE_NODE)

            // 假如代码执行到这里, 说明所有祖先的容量都足够存放新的文件, 可以执行更新
            currentNode = endpoint // 复位
            do {
                if (currentNode.hasCapacityRestrict()) {
                    currentNode.cutCapAvailable(amount)
                    onChange.handle(currentNode)
                }
                currentNode = parent(currentNode)
            } while (currentNode != SentryNode.FAKE_NODE)
        }

        fun releaseCap(path: String, amount: Long, onChange: CapacityChangeHandler) {
            if (amount == 0L)
                return

            val (key, node, hit) = ref.findAndMatch(path) { it.hasCapacityRestrict() }
            var currentNode = if (hit) node else ref.findNode(key)

            do {
                if (currentNode.hasCapacityRestrict()) {
                    currentNode.releaseCapAvailable(amount)
                    onChange.handle(currentNode)
                }
                currentNode = parent(currentNode)
            } while (currentNode != SentryNode.FAKE_NODE)
        }

        fun deleteRule(path: String) {
            val node = ref.findNode(path)
            if (!node.valid)
                return
            node.ruleInvalidate()
            if (node.subItems.isEmpty()) {
                deepDeleteNode(node)
            }
        }

        fun deleteLimit(path: String) {
            val node = ref.findNode(path)
            if (!node.valid)
                return
            node.limitInvalidate()
            if (node.subItems.isEmpty()) {
                deepDeleteNode(node)
            }
        }

        fun updateCapAvailable(path: String, used: Long) {
            findNode(path).updateCapAvailableByUsed(used)
        }

        fun updateRule(path: String, newRule: ByteArray): SentryNode {
            val node = ref.findNode(path)
            for (idx in node.rule.indices) {
                node.rule[idx] = newRule[idx]
            }
            return node
        }

        private fun deepDeleteNode(node: SentryNode) {
            if (node.valid)
                return

            var subNode: SentryNode = node
            var supNode: SentryNode = parent(subNode)
            while (supNode != SentryNode.FAKE_NODE) {
                if (!supNode.valid) {
                    subNode = supNode
                    supNode = parent(supNode)
                } else {
                    break
                }
            }

            if (supNode != SentryNode.FAKE_NODE && supNode.valid) {
                supNode.deleteSubItem(subNode.itemKey)
            }
        }

        private fun capacityShortageException(path: String, node: SentryNode): UploadExceedLimitException {
            return UploadExceedLimitException(capacityShortageReport(path, node))
        }

        private fun capacityShortageReport(path: String, node: SentryNode) = UploadExceedLimitReporter(
            path, node.fullPath(), node.capacityLimit(), node.capacityAvailable()
        )

        private fun createIllegalLimitError(createPath: String, node: SentryNode) = CreateIllegalLimitException(
            reportIllegalLimit(createPath, node)
        )

        private fun reportIllegalLimit(createPath: String, node: SentryNode) =
            CreateIllegalLimitReporter(createPath, node.fullPath(), node.capacityLimit())
    }

    class Viewer(private val ref: VirtualFileSystem) {

        fun getRuleNode(path: String): SentryNode {
            val (key, node, hit) = findAndMatch(path) { it.hasRuleRestrict() }
            return if (hit) node
            else findNode(key)
        }

        fun getCapNode(path: String): SentryNode {
            val (key, node, hit) = findAndMatch(path) { it.hasCapacityRestrict() }
            return if (hit) node
            else findNode(key)
        }

        fun listCapLimitPaths(): List<String> {
            return LinkedList<String>().apply {
                traversalByLevel(ref.root).forEach { node ->
                    if (node.hasCapacityRestrict()) {
                        addFirst(node.fullPath())
                    }
                }
            }
        }

        internal inline fun listSubNodes(node: SentryNode, match: (SentryNode) -> Boolean): List<SentryNode> {
            return traversalByLevel(node).filter(match)
        }

        internal inline fun listSupNodes(node: SentryNode, match: (SentryNode) -> Boolean): List<SentryNode> {
            val res = ArrayList<SentryNode>()
            var currentNode = parent(node)
            while (currentNode != SentryNode.FAKE_NODE) {
                if (match(currentNode))
                    res.add(currentNode)
                currentNode = parent(currentNode)
            }
            return res
        }

        private fun traversalByLevel(root: SentryNode): List<SentryNode> {
            val res = LinkedList<SentryNode>()
            val que = LinkedList<SentryNode>()

            que.addLast(root)
            while (que.isNotEmpty()) {
                val node = que.removeFirst()
                res.add(node)
                for ((_, subNode) in node.subItems) {
                    que.addLast(subNode)
                }
            }

            return res
        }
    }

    fun interface CapacityChangeHandler {
        fun handle(path: String, limit: Long, free: Long)
        fun handle(node: SentryNode) {
            handle(node.fullPath(), node.capacityLimit(), node.capacityAvailable())
        }
    }
}